#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "httpd.h"


/*
 * On naming conventions:
 * 
 * HTTP/1.1 defines message-header as 
 * 
 * message-header = field-name ":" [ field-value ] CRLF
 * field-name     = token
 * field-value    = *( field-content | LWS )
 * 
 * HTTP/1.1 does not give a name name a group of all message-headers in a message.
 
 * local constants and vars

 * A table with major attributes for every known field. 
 * We calculate name lengths and reorganize this array on start up. 
 * After reorganization, field id can be used as an index to the table.
 */

static key_value http_versions[] = {
	{ HTTP_VERSION_1_1, "HTTP/1.1" },
	{ HTTP_VERSION_1_0, "HTTP/1.0" },
	{ HTTP_VERSION_UNSET, NULL }
};

static key_value http_methods[] = {
	{ HTTP_METHOD_GET,  "GET" },
	{ HTTP_METHOD_POST, "POST" },
	{ HTTP_METHOD_HEAD, "HEAD" },
	{ HTTP_METHOD_PROPFIND, "PROPFIND" },
	{ HTTP_METHOD_PROPPATCH, "PROPPATCH" },
	{ HTTP_METHOD_REPORT, "REPORT" },
	{ HTTP_METHOD_OPTIONS, "OPTIONS" },
	{ HTTP_METHOD_MKCOL, "MKCOL" },
	{ HTTP_METHOD_PUT, "PUT" },
	{ HTTP_METHOD_DELETE, "DELETE" },
	{ HTTP_METHOD_COPY, "COPY" },
	{ HTTP_METHOD_MOVE, "MOVE" },
	{ HTTP_METHOD_LABEL, "LABEL" },
	{ HTTP_METHOD_CHECKOUT, "CHECKOUT" },
	{ HTTP_METHOD_CHECKIN, "CHECKIN" },
	{ HTTP_METHOD_MERGE, "MERGE" },
	{ HTTP_METHOD_LOCK, "LOCK" },
	{ HTTP_METHOD_UNLOCK, "UNLOCK" },
	{ HTTP_METHOD_MKACTIVITY, "MKACTIVITY" },
	{ HTTP_METHOD_UNCHECKOUT, "UNCHECKOUT" },
	{ HTTP_METHOD_VERSION_CONTROL, "VERSION-CONTROL" },
	{ HTTP_METHOD_CONNECT, "CONNECT" },
	{ HTTP_METHOD_UNSET, NULL }
};

static key_value http_status[] = {
	{ 100, "Continue" },
	{ 101, "Switching Protocols" },
	{ 102, "Processing" }, /* WebDAV */
	{ 200, "OK" },
	{ 201, "Created" },
	{ 202, "Accepted" },
	{ 203, "Non-Authoritative Information" },
	{ 204, "No Content" },
	{ 205, "Reset Content" },
	{ 206, "Partial Content" },
	{ 207, "Multi-status" }, /* WebDAV */
	{ 300, "Multiple Choices" },
	{ 301, "Moved Permanently" },
	{ 302, "Found" },
	{ 303, "See Other" },
	{ 304, "Not Modified" },
	{ 305, "Use Proxy" },
	{ 306, "(Unused)" },
	{ 307, "Temporary Redirect" },
	{ 400, "Bad Request" },
	{ 401, "Unauthorized" },
	{ 402, "Payment Required" },
	{ 403, "Forbidden" },
	{ 404, "Not Found" },
	{ 405, "Method Not Allowed" },
	{ 406, "Not Acceptable" },
	{ 407, "Proxy Authentication Required" },
	{ 408, "Request Timeout" },
	{ 409, "Conflict" },
	{ 410, "Gone" },
	{ 411, "Length Required" },
	{ 412, "Precondition Failed" },
	{ 413, "Request Entity Too Large" },
	{ 414, "Request-URI Too Long" },
	{ 415, "Unsupported Media Type" },
	{ 416, "Requested Range Not Satisfiable" },
	{ 417, "Expectation Failed" },
	{ 422, "Unprocessable Entity" }, /* WebDAV */
	{ 423, "Locked" }, /* WebDAV */
	{ 424, "Failed Dependency" }, /* WebDAV */
	{ 426, "Upgrade Required" }, /* TLS */
	{ 500, "Internal Server Error" },
	{ 501, "Not Implemented" },
	{ 502, "Bad Gateway" },
	{ 503, "Service Not Available" },
	{ 504, "Gateway Timeout" },
	{ 505, "HTTP Version Not Supported" },
	{ 507, "Insufficient Storage" }, /* WebDAV */

	{ -1, NULL }
};

// functions define here.
static const char *get_value(key_value* kv, int k);

static int get_key(key_value* kv, const char *s, size_t len);

void http_request_free (http_request* req);
void http_response_free(http_response* resp);

int httpd_readchar(connection* conn, char *ch);

int http_request_parse_first_line(http_request *request, const char* ptr, size_t len);

int http_request_parse_header(connection *conn);

int http_request_parse_data(connection *conn);

int http_listen(int port, int theards)
{
	int fd, flag = 1;
	struct sockaddr_in addr;
	struct linger ling;
	if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket");
		return -1;
	}

	memset(&addr, 0x0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port   = htons(port);
	addr.sin_addr.s_addr = htons(INADDR_ANY);
	
	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) == -1)
	{
		perror("setsockopt");
		return -1;		
	}

	memset(&ling, 0x0, sizeof(ling));
	ling.l_onoff  = 1;
	ling.l_linger = 30;
	if(setsockopt(fd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) == -1)
	{
		perror("setsockopt");
		return -1;		
	}

	if(bind(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1)
	{
		perror("bind");
		return -1;
	}

	if(listen(fd, theards + 10) == -1)
	{
		perror("listen");
		return -1;
	}
	
	return fd;
}

const char *get_value(key_value* kv, int k)
{
	int i;
	for(i = 0; kv[i].value; i++) {
		if(kv[i].key == k) return kv[i].value;
	}
	return NULL;

}

int get_key(key_value* kv, const char *s, size_t len)
{
	int i;
	for(i = 0; kv[i].value; i++){
		if (0 == strncmp(kv[i].value, s, len)) return kv[i].key;
	}
	return -1;	
}

const char* http_var_get(http_var *var, const char* name)
{
	http_var *vptr;
	vptr = var->next;
	while(vptr)
	{
		if(0 == strcmp(vptr->name, name)) return vptr->value;
		
		vptr = vptr->next;
	}
	
	return NULL;
}

http_var *httpd_var_end(http_var *var)
{
	http_var *vptr;
	vptr = var;
	
	while(vptr->next) vptr = vptr->next;
	return 	vptr;
}

const char* http_get_error(int key)
{
	return get_value(http_status, key);
}

int httpd_readchar (connection* conn, char *ch)
{
start_readchar:	
	if(conn->bufstat == CONNE_BUFSTAT_UNSET ||
			conn->rptr == conn->readbuf + conn->bufsize)
		goto do_read;
	
	*ch = *conn->rptr++;
	conn->bufpos++;
	return 1;
	
do_read:
	conn->bufsize = httpd_read(conn->client.fd, conn->readbuf, DEFAULT_LIMIT_REQUEST_LINE);
	conn->rptr = conn->readbuf;
	conn->bufpos = 0;
	
	if(conn->bufsize == -1)
	{
		conn->bufstat = CONNE_BUFSTAT_FAILED;
		return -1;
	}

	if(conn->bufsize == 0)
	{
		conn->bufstat = CONNE_BUFSTAT_END;
		return 0;
	}
	
	if(conn->bufsize == DEFAULT_LIMIT_REQUEST_LINE) 
		conn->bufstat = CONNE_BUFSTAT_FULL;
	else
		conn->bufstat = CONNE_BUFSTAT_READY;
	
	goto start_readchar;
}

ssize_t httpd_read(int fd, char *vptr, size_t len)
{
	size_t left;
	char *ptr;
	ssize_t n = 0;
	
	ptr = vptr;
	left = len;
	while(left > 0)
	{
		if((n = read(fd, ptr, left)) == -1)
		{
			if(errno == EINTR) continue;
			else return -1;	
		}
		
		left -= n;
		if(ptr[n-1] == LF || n == 0) break;
		ptr  += n;
	}
	
	return len - left;
}

ssize_t httpd_write(int fd, const void *vptr, size_t len)
{
	size_t left;
	ssize_t n;
	const char *ptr;
	
	ptr = vptr;
	left = len;
	while(left > 0)
	{
		if((n = write(fd, ptr, left)) == -1)
		{
			if(errno == EINTR) continue;
			else return -1;
		}
		
		left -= n;
		ptr  += n;
	}

	return len - left;
}

ssize_t httpd_readline(connection* conn, char *vptr, size_t len)
{
	size_t i;
	char c, *ptr;
	int rc;
	ptr = vptr;
	
	for(i = 0; i < len; i++)
	{
		if((rc = httpd_readchar(conn, &c)) == 1)
		{
			*ptr++ = c;
			if(c == LF) break;

			continue;
		}
		
		// here will return
		
		// when EOF
		if(rc == 0) return i;
		
		// when Failed 
		return -1;
	}
	
	return i;
}

int http_conn_init(connection* conn, const http_client *cli)
{
	http_request  *request;
	http_response *response;
	http_var *ptr;
	
	memset(conn, 0x0, sizeof(connection));
	memcpy(&conn->client, cli, sizeof(http_client));
	conn->bufstat = CONNE_BUFSTAT_UNSET;
	conn->rptr = conn->readbuf;
	conn->bufsize = 0;
	conn->bufpos  = 0;
	
	request  = &conn->request;
	response = &conn->response;
	response->version = HTTP_VERSION_1_1;
	request->header_entry = (http_var *)malloc(sizeof(http_var));
	if(!request->header_entry) return -1;
	memset(request->header_entry, 0x0, sizeof(http_var));
	response->header_entry = (http_var *)malloc(sizeof(http_var));
	if(!response->header_entry)
	{
		free(request->header_entry);
		request->header_entry = NULL;
		return -1;
	}
	memset(response->header_entry, 0x0, sizeof(http_var));
	
	ptr = response->header_entry;
	httpd_var_append(ptr, HTTP_HEADER_SERVER, 7, "Http Queue", 11);
	ptr = ptr->next;
	httpd_var_append(ptr, HTTP_HEADER_CONTENT_TYPE, 13, DEFAULT_CONTENT_TYPE, 11);
	ptr = ptr->next;
	httpd_var_append(ptr, HTTP_HEADER_CONNECTION, 11, "close", 6);
	response->content = (char*)malloc(DEFAULT_LIMIT_RESPONSE_DATASIZE - 4096);
	if(!response->content)
	{
		free(request ->header_entry);
		free(response->header_entry);
		request ->header_entry = NULL;
		response->header_entry = NULL;
		return -1;
	}
	
	return 1;
}

int http_request_parse_first_line(http_request *request, const char* ptr, size_t len)
{
	int first, i, request_line_stage = 0;
	int ms = 0, us = 0, vs = 0;
	int path_size = 0, content_length = 0;
	const char *t, *cur, *method = NULL, *uri = NULL, *ver = NULL;
	const char *interg = NULL;
	http_method_t r;
	http_version_t v;
	
	first = 0;
	cur = ptr;
	for(i = 0; i < len; i++, cur++)
	{
		switch(*cur) {
		case CR:
		case LF:
			if(request_line_stage != 2) return HTTP_BAD_REQUEST;
			
			r = (http_method_t)get_key(http_methods, method, ms);
			if(r == -1) return HTTP_NOT_IMPLEMENTED;
			
			vs = i - first;
			ver = ptr + first;
			v  = (http_version_t)get_key(http_versions, ver, vs);
			if(v == -1) return HTTP_BAD_REQUEST;
			
			request->method  = r;
			request->version = v;
			request->uri = (char*)malloc(us + 1);
			if(!request->uri) return HTTP_INTERNAL_SERVER_ERROR;
			memcpy(request->uri, uri, us);
			request->uri[us] = '\0';
			if(NULL == (interg = strchr(request->uri, '?')))
				path_size = us;
			else
			{
				path_size = interg - request->uri;
				content_length = us - path_size - 1;
			}
			
			request->path = (char*)malloc(path_size + 1);
			if(!request->path) return HTTP_INTERNAL_SERVER_ERROR;
			memcpy(request->path, request->uri, path_size);
			request->path[path_size] = '\0';	
			if(content_length != 0)
			{
				request->content = (char*)malloc(content_length + 1);
				if(!request->content) return HTTP_INTERNAL_SERVER_ERROR;
				
				memcpy(request->content, interg + 1, content_length);
				request->content[content_length] = '\0';
				request->content_length = content_length;
			}
			
			return 1;
			
		case ' ':
			switch(request_line_stage) {
			case 0:
				method = ptr;
				first  = i + 1;
				ms = i;
				break;
			case 1:
				uri = ptr + first;
				us  = i - first;
				first = i + 1;
								
				if(0 == strncasecmp(uri, "http://", 7))
				{
					uri += 7;
					if(NULL == (t = strchr(uri, '/')))
						return HTTP_BAD_REQUEST;
					
					us -= t - uri + 7;
					uri = t;
				}
				
				break;
			default:
				return HTTP_BAD_REQUEST;
			}
			
			request_line_stage++;
			break;
		}
	}
	
	return HTTP_INTERNAL_SERVER_ERROR;
}

int http_request_parse_header(connection *conn)
{
	http_var *vptr;
	int sn, sv;
	char *name, *value, *ptr, *pp;
	char buffer[DEFAULT_LIMIT_REQUEST_LINE + 1];
	ssize_t buf_used;
	
	vptr = conn->request.header_entry;
loop:
	buf_used = httpd_readline(conn, buffer, DEFAULT_LIMIT_REQUEST_LINE);

	if(buf_used == -1) return HTTP_BAD_REQUEST;
	
	ptr = buffer;

	if(*ptr == CR || *ptr == LF || buf_used == 0)
		return 1;

	while(*ptr == ' ') ptr++;
	pp = strchr(ptr, ':');
	if(pp == NULL) return HTTP_BAD_REQUEST;
	
	name = ptr;
	sn = pp - ptr;
	while(ptr[sn-1] == ' ') sn--; 

	while(*++pp == ' ');
	value = pp;
	sv =  buf_used - (pp - buffer) - 1;

	if(-1 == httpd_var_append(vptr, name, sn, value, sv))
		return HTTP_BAD_REQUEST;

	vptr = vptr->next;
	goto loop;
}

int http_request_parse_data(connection *conn)
{
	int length = 0;
	size_t size = 0;
	const char *c_len;
	char *ptr, *content;
	
	if(conn->request.method != HTTP_METHOD_POST) return 1;
	
	c_len = http_var_get(conn->request.header_entry, "Content-Length");
	if(!c_len) return HTTP_BAD_REQUEST;
	
	length = atoi(c_len);
	ptr = content = (char *)malloc(length + conn->request.content_length + 8);
	if(!content) return HTTP_INTERNAL_SERVER_ERROR;
	
	if(conn->request.content_length != 0)
	{
		memcpy(ptr, conn->request.content, conn->request.content_length);
		free(conn->request.content);
		
		ptr += conn->request.content_length;
		*ptr++ = '&';
	}

	if(conn->rptr != conn->readbuf + conn->bufsize)
	{
		size = conn->bufsize - conn->bufpos;
		if(size < length)
		{
			memcpy(ptr, conn->rptr, size);
			ptr    += size;
			length -= size;
		}
		else
		{
			memcpy(ptr, conn->rptr, length);
			goto parse_end;
		}
	}
	
	if(-1 == httpd_read(conn->client.fd, ptr, length))
	{
		return HTTP_INTERNAL_SERVER_ERROR;
	}

parse_end:
	ptr[length] = '\0';
	conn->request.content_length += length;
	conn->request.content = content;
	return 1;
}

int http_request_parse(connection* conn)
{
	/*
	 * Request: "^(GET|POST|HEAD) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$"
	 * Option : "^([-a-zA-Z]+): (.+)$"
	 * End    : "^$"
	 */
	
	char buffer[DEFAULT_LIMIT_REQUEST_LINE + 1];
	ssize_t buf_used;
	
	int response_code;
	if(1 > (buf_used = httpd_readline(conn, buffer, DEFAULT_LIMIT_REQUEST_LINE)))
	{
		// TODO : set response code.
		return HTTP_BAD_REQUEST;
	}

	/* parse the first line of the request
	 *
	 * should be:
	 *
	 * <method> <uri> <protocol>\r\n
	 * */
	response_code = http_request_parse_first_line(&conn->request, buffer, buf_used);
	if(1 != response_code) return response_code;
	
	response_code = http_request_parse_header(conn);
	if(1 != response_code) return response_code;
	
	response_code = http_request_parse_data(conn);
	if(1 != response_code) return response_code;
	
	return HTTP_OK;
}

int httpd_var_append(http_var *vptr, const char *name, size_t sn, const char *value, size_t sv)
{
	http_var *newvar;
	newvar = (http_var *)malloc(sizeof(http_var));
	if(newvar == NULL) return -1;
	
	newvar->name  = (char *)malloc(sn + 1);
	newvar->value = (char *)malloc(sv + 1);
	if(!newvar->value)
	{
		free(newvar->name);
		newvar->name = NULL;
		return -1;
	}
	
	memcpy(newvar->name, name, sn);
	newvar->name[sn]  = '\0';
	memcpy(newvar->value, value, sv);
	newvar->value[sv] = '\0';
	newvar->next = NULL;

	vptr->next = newvar;
	return 1;
}

int http_cookie_parse(const char *str, size_t len, http_var **entry)
{
	const char *ptr, *rptr;
	char *name, *value;
	int offset = 0;
	http_var *item, *list;
	
	// list head
	list = *entry = (http_var*)malloc(sizeof(http_var));
	if(list == NULL) return -1;
	memset(list, 0 , sizeof(http_var));
	
	rptr = ptr = str;
	while(NULL != (rptr = strchr(ptr, '=')))
	{
		item = (http_var*)malloc(sizeof(http_var));
		if(item == NULL) return -1;
		memset(item, 0 , sizeof(http_var));
		while(*ptr == ' ') ptr++;
		offset = rptr - ptr;
		while(ptr[offset - 1] == ' ') offset--;
		name = (char *)malloc(offset + 1);
		if(name == NULL) return -1;
		
		memcpy(name, ptr, offset);
		name[offset] = '\0';
		
		ptr = rptr;
		while(*ptr++ == ' ');
		if((rptr = strchr(ptr, ';')) != NULL)
			offset = rptr - ptr;
		else
			offset = len - (ptr - str);

		while(ptr[offset - 1] == ' ') offset--;
		value = (char *)malloc(offset + 1);
		if(value == NULL) return -1;
		
		memcpy(value, ptr, offset);
		value[offset] = '\0';
		
		item->name = name;
		item->value = value;
		item->next = NULL;
		list->next = item;
		list = list->next;
		
		ptr = rptr + 1;
		if(rptr == NULL) return 1;
	}
	
	return -1;
}

int http_request_call(http_request* req, int timeout, char *resp, int len)
{
	const char *hpp, *host, *vers;
	const char *hh = http_var_get(req->header_entry, "Host");
	char hop[64];
	int port;
	struct sockaddr_in sin;
	struct hostent *he;
	
	int sock, n;
	char *ptr;
	http_var *vptr;
	int left = DEFAULT_LIMIT_REQUEST_LINE;
	char buffer[DEFAULT_LIMIT_REQUEST_LINE + 2];
	
	fd_set rfds;
	struct timeval tv;
	tv.tv_sec  = timeout;
	tv.tv_usec = 0;
	if((hpp = strstr(hh, ":")) != NULL)
	{
		int l = hpp++ - hh;
		strncpy(hop, hh, l);
		port = atoi(hpp);
		host = hop;
	}
	else
	{
		host = hh;
		port = 80;	
	}
	he = gethostbyname(host);
	if(!he) return -1;
	
	sin.sin_family=he->h_addrtype;
	sin.sin_port = htons(port);
	sin.sin_addr.s_addr=((unsigned long*)(he->h_addr_list[0]))[0];
	if((sock=socket(AF_INET, SOCK_STREAM, 0)) < 0) return -1;
	if(connect(sock,(struct sockaddr*)&sin,sizeof(sin)) <0 )
	{
		close(sock); return -1;
	}
	
	vers = get_value(http_versions, req->version);
	vptr = req->header_entry->next;
	ptr = buffer;
	if(req->method == HTTP_METHOD_POST)
		snprintf(ptr, DEFAULT_LIMIT_REQUEST_LINE, "%s %s %s\r\n", "POST", req->path, vers);
	else
		snprintf(ptr, DEFAULT_LIMIT_REQUEST_LINE, "%s %s?%s %s\r\n", "GET", req->path, req->content, vers);
	ptr  += strlen(ptr);
	left -= strlen(ptr);
	while(vptr)
	{
		snprintf(ptr, left, "%s: %s\r\n", vptr->name, vptr->value);
		ptr  += strlen(ptr);
		left -= strlen(ptr);
		vptr = vptr->next;
		
		if(left < 3) break;
	}
	memcpy(ptr, "\r\n\0", 3);	
	if(-1 == httpd_write(sock, buffer, strlen(buffer))) return -1;
	if(HTTP_METHOD_POST == req->method)
	{
		if(-1 == httpd_write(sock, req->content, req->content_length)) return -1;
	}

	FD_ZERO(&rfds);
	FD_SET(sock, &rfds);
	if(!select(sock+1, &rfds, NULL, NULL, &tv))
	{
		close(sock); return -1;
	}
	
	if(!FD_ISSET(sock,&rfds))
	{
		close(sock); return -1;
	}

	left = len;
	ptr  = resp;
	while(0 < (n = httpd_read(sock, ptr, left)))
	{
		left -= n;
		ptr  += n;
	}
	
	close(sock);
	if(n == -1) return -1;
	
	return (resp[9] == '2') ? 1 : -1;
}

int http_response_call(connection* conn)
{
	char *ptr;
	char r[DEFAULT_LIMIT_RESPONSE_DATASIZE];
	http_response *response;
	int length = 0, ll;
	const char *errmsg;
	http_var *pp;
	
	memset(r, 0x0, DEFAULT_LIMIT_RESPONSE_DATASIZE);
	response = &conn->response;
	ptr = r;
	errmsg = http_get_error(response->code);
	snprintf(ptr, 1024, "%s %d %s\r\n",
		DEFAULT_SERVER_PROTOCOL, response->code, errmsg);

	ll = strlen(ptr);
	ptr = ptr + ll;
	length += ll;
	pp = response->header_entry->next;
	while(pp)
	{
		snprintf(ptr, 1024, "%s: %s\r\n",
			pp->name, pp->value);

		ll = strlen(ptr);
		pp = pp->next;
		ptr = ptr + ll;
		length += ll;
	}
	
	ll = response->content_length + 3;
	snprintf(ptr, ll, "\r\n%s\n", response->content);
	length += ll;
	if(-1 == httpd_write(conn->client.fd, r, length))
	{
		return -1;
	}
	
	return 1;
}

void http_var_free(http_var **entry)
{
	http_var *pre, *pp;
	
	pre = *entry;
	while(pre)
	{
		pp = pre->next;
		if(pre->name) free(pre->name);
		if(pre->value) free(pre->value);
		free(pre);
		pre = pp;
	}
	
	*entry = NULL;	
}

void http_request_free(http_request* req)
{
	if(req->uri)     free(req->uri);
	if(req->path)    free(req->path);
	if(req->content) free(req->content);
	req->uri     = NULL;
	req->path    = NULL;
	req->content = NULL;
	
	http_var_free(&req->header_entry);
}

void http_response_free(http_response* resp)
{
	if(resp->content) free(resp->content);
	resp->content = NULL;
	
	http_var_free(&resp->header_entry);
}

void http_conn_fini(connection* conn)
{
	http_request_free(&conn->request);
	http_response_free(&conn->response);

	memset(conn, 0x0, sizeof(connection));
}
